﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace MOD_OlBGDZ
{
    public class ToolBox
    {
        public static string ToRawString(object @object)
        {
            if (@object is null) { throw new Exception($"{nameof(ToRawString)} call with null"); }
            else if (@object is bool @bool) { return @bool.ToString(CultureInfo.InvariantCulture); }
            else if (@object is int @int) { return @int.ToString(CultureInfo.InvariantCulture); }
            else if (@object is short @short) { return @short.ToString(CultureInfo.InvariantCulture); }
            else if (@object is long @long) { return @long.ToString(CultureInfo.InvariantCulture); }
            else if (@object is float @float) { return @float.ToString("G", CultureInfo.InvariantCulture); }
            else if (@object is double @double) { return @double.ToString("G", CultureInfo.InvariantCulture); }
            else { return @object.ToString() ?? throw new Exception($"{nameof(ToRawString)} return null"); }
        }

        /// <param name="argumentsWrapperType">The type of <see cref="ValueTuple{}"/>, <see cref="ValueTuple{,}"/>, <see cref="ValueTuple{,,}"/>...</param>
        public static bool TryParseDramaFunction(string stringRepresentation, Type argumentsWrapperType, out ValueTuple<string, ITuple> dramaFunction)
        {
            dramaFunction = default;
            Type[] genericArguments = argumentsWrapperType.GetGenericArguments();
            int genericArity = genericArguments.Length;
            int requiredParamCount = genericArguments.Count(type => Nullable.GetUnderlyingType(type) == null);
            string[] parts = stringRepresentation.Split('_');
            string dramaFunctionName = parts[0];
            string[] dramaFunctionArguments = parts.Skip(1).ToArray();
            if (dramaFunctionArguments.Length < requiredParamCount)
            {
                Console.WriteLine($"\"{dramaFunctionName}\" expected to require at least {requiredParamCount} parameters, but got {dramaFunctionArguments.Length} instead");
                return false;
            }
            if (dramaFunctionArguments.Length > genericArity)
            {
                Console.WriteLine($"{dramaFunctionName} expected to accept at most {genericArity} parameters, but got {dramaFunctionArguments.Length} instead");
                return false;
            }
            var ctor = argumentsWrapperType.GetConstructor(genericArguments);
            if (ctor == null)
            {
                Console.WriteLine($"{argumentsWrapperType} has no valid constructor");
                return false;
            }
            var parsedTo = Enumerable.Range(0, genericArity)
                .Select(i => i < dramaFunctionArguments.Length
                    ? Convert.ChangeType(
                        dramaFunctionArguments[i],
                        i < requiredParamCount ? genericArguments[i] : Nullable.GetUnderlyingType(genericArguments[i]) ?? throw new Exception("Unknown behavior"),
                        CultureInfo.InvariantCulture)
                    : null)
                .ToArray();
            dramaFunction = (dramaFunctionName, (ITuple)ctor.Invoke(parsedTo));
            return true;
        }

        /// <typeparam name="T">The type of <see cref="ValueTuple{}"/>, <see cref="ValueTuple{,}"/>, <see cref="ValueTuple{,,}"/>...</typeparam>
        public static bool TryParseDramaFunction<T>(string stringRepresentation, out ValueTuple<string, T> dramaFunction) where T : ITuple
        {
            dramaFunction = default;
            bool result = TryParseDramaFunction(stringRepresentation, typeof(T), out ValueTuple<string, ITuple> innerDramaFunction);
            if (result == true)
            {
                dramaFunction = (innerDramaFunction.Item1, (T)innerDramaFunction.Item2);
            }
            return result;
        }

        public static string StringizeDramaFunction((string Name, ITuple Arguments) dramaFunction)
        {
            int nonNullArgumentsCount = Enumerable.Range(0, dramaFunction.Arguments.Length)
                .Count(i => dramaFunction.Arguments[i] != null);
            return string.Join(
                "_",
                Enumerable.Concat(
                    new List<string>() { dramaFunction.Name },
                    Enumerable.Range(0, nonNullArgumentsCount)
                        .Select(i => ToRawString(dramaFunction.Arguments[i] ?? throw new Exception("Unknown behavior")))
                ).ToArray()
            );
        }
    }
}
